// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2024.

use core::arch::naked_asm;

#[unsafe(naked)]
#[unsafe(no_mangle)]
pub extern "C" fn handler_entry() {
    naked_asm!(
        "
    # Save CPU state of the interrupted procedure. We will be calling cdecl functions, so we only
    # need to save eax/ecx/edx (plus anything else we want to use directly).
    push    eax
    push    ecx
    push    edx

    # Current stack layout:
    #
    # [esp+36]      SS                  (only if user mode was interrupted)
    # [esp+32]      ESP                 (only if user mode was interrupted)
    # [esp+28]      EFLAGS
    # [esp+24]      CS
    # [esp+20]      EIP
    # [esp+16]      Error code          (maybe zero)
    # [esp+12]      Interrupt number
    # [esp+8]       EAX
    # [esp+4]       ECX
    # [esp]         EDX

    # Check for system call
    cmp     dword ptr [esp+12], 0x40
    je      return_from_user

    # Check for a CPU-generated exception
    cmp     dword ptr [esp+12], 0x20
    jl      2f

    # Otherwise, we assume this is an interrupt from an external device. Call into the external
    # interrupt handler provided by the current chip.
    push    dword ptr [esp+12]
    call    handle_external_interrupt
    add     esp, 4

    # If CS was anything besides segment 1, then a user app was running
    #
    # We discard the high-order 16 bits before comparing because they are undefined. Some CPUs may
    # clear them when pushing CS, while others may leave them untouched.
    and     dword ptr [esp+24], 0x0000FFFF
    cmp     dword ptr [esp+24], 0x8
    jne     return_from_user

    # Else the interrupt happened in kernel mode, so we can jump straight back

    # Restore CPU state
    pop     edx
    pop     ecx
    pop     eax

    # Pop interrupt number and error code
    add     esp, 4
    add     esp, 4

    iretd

2:

    # If CS was anything besides segment 1, then a user app was running
    #
    # We discard the high-order 16 bits before comparing because they are undefined. Some CPUs may
    # clear them when pushing CS, while others may leave them untouched.
    and     dword ptr [esp+24], 0x0000FFFF
    cmp     dword ptr [esp+24], 0x8
    jne     return_from_user

    # Otherwise, we treat this as a CPU exception generated by kernel code. We pass the exception
    # number and error code (if any) to the handle_kernel_exception function.
    push    dword ptr [esp+20]
    push    dword ptr [esp+20]
    push    dword ptr [esp+20]
    call    handle_kernel_exception

    # handle_kernel_exception should never return, but just in case...
3:
    jmp     3b

"
    );
}
